package kmi.droid.livewallpaper.gl;

import java.util.ArrayList;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import android.util.Log;
import android.view.SurfaceHolder;

class GLPaintingThread extends Thread
{
	private final static boolean LOG_THREADS = false;
	public final static int DEBUG_CHECK_GL_ERROR = 1;
	public final static int DEBUG_LOG_GL_CALLS = 2;

	private final GLThreadManager _glThreadManager = new GLThreadManager();
	private GLPaintingThread _eglOwner;

	private EGLConfigChooserBase _eglConfigChooser;
	private EGLFactory.EGLContextFactory _eglContextFactory;
	private EGLFactory.EGLWindowSurfaceFactory _eglWindowSurfaceFactory;
	private GLWrapper _glWrapper;

	public SurfaceHolder mHolder;
	private boolean _sizeChanged = true;

	// Once the thread is started, all accesses to the following member
	// variables are protected by the sGLThreadManager monitor
	public boolean _done;
	private boolean _paused;
	private boolean _hasSurface;
	private boolean _waitingForSurface;
	private boolean _haveEgl;
	private int _width;
	private int _height;
	private int _renderMode;
	private boolean _requestRender;
	private boolean _eventsWaiting;
	// End of member variables protected by the sGLThreadManager monitor.

	private GLRenderer _renderer;

	private List<Runnable> _eventQueue = new ArrayList<Runnable>();
	private EGLHelper _eglHelper;

	GLPaintingThread(GLRenderer renderer, EGLConfigChooserBase chooser,
			EGLFactory.EGLContextFactory contextFactory,
			EGLFactory.EGLWindowSurfaceFactory surfaceFactory, GLWrapper wrapper)
	{
		super();

		_done = false;
		_width = 0;
		_height = 0;
		_requestRender = true;
		_renderMode = GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY;
		_renderer = renderer;

		_eglConfigChooser = chooser;
		_eglContextFactory = contextFactory;
		_eglWindowSurfaceFactory = surfaceFactory;
		_glWrapper = wrapper;
	}

	@Override
	public void run()
	{
		setName("GLThread " + getId());
		if (LOG_THREADS)
			Log.i("GLThread", "starting tid=" + getId());

		try
		{
			guardedRun();
		} catch (InterruptedException e)
		{
			// fall thru and exit normally
		} finally
		{
			_glThreadManager.threadExiting(this);
		}
	}

	/*
	 * This private method should only be called inside a
	 * synchronized(sGLThreadManager) block.
	 */
	private void stopEglLocked()
	{
		if (_haveEgl)
		{
			_haveEgl = false;

			_eglHelper.destroySurface();
			_glThreadManager.releaseEglSurface(this);
		}
	}

	private void guardedRun() throws InterruptedException
	{
		_eglHelper = new EGLHelper(_eglConfigChooser, _eglContextFactory,
				_eglWindowSurfaceFactory, _glWrapper);
		try
		{
			GL10 gl = null;
			boolean tellRendererSurfaceCreated = true;
			boolean tellRendererSurfaceChanged = true;

			/*
			 * This is our main activity thread's loop, we go until asked to
			 * quit.
			 */
			while (!isDone())
			{
				/*
				 * Update the asynchronous state (window size)
				 */
				int w = 0;
				int h = 0;
				boolean changed = false;
				boolean needStart = false;
				boolean eventsWaiting = false;

				synchronized (_glThreadManager)
				{
					while (true)
					{
						// Manage acquiring and releasing the SurfaceView
						// surface and the EGL surface.
						if (_paused)
						{
							stopEglLocked();
						}
						if (!_hasSurface)
						{
							if (!_waitingForSurface)
							{
								stopEglLocked();
								_waitingForSurface = true;
								_glThreadManager.notifyAll();
							}
						} else
						{
							if (!_haveEgl)
							{
								if (_glThreadManager.tryAcquireEglSurface(this))
								{
									_haveEgl = true;
									_eglHelper.start();
									_requestRender = true;
									needStart = true;
								}
							}
						}

						// Check if we need to wait. If not, update any state
						// that needs to be updated, copy any state that
						// needs to be copied, and use "break" to exit the
						// wait loop.

						if (_done)
							return;

						if (_eventsWaiting)
						{
							eventsWaiting = true;
							_eventsWaiting = false;
							break;
						}

						if ((!_paused)
								&& _hasSurface
								&& _haveEgl
								&& (_width > 0)
								&& (_height > 0)
								&& (_requestRender || (_renderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY)))
						{
							changed = _sizeChanged;
							w = _width;
							h = _height;
							_sizeChanged = false;
							_requestRender = false;
							if (_hasSurface && _waitingForSurface)
							{
								changed = true;
								_waitingForSurface = false;
								_glThreadManager.notifyAll();
							}
							break;
						}

						// By design, this is the only place where we wait().

						if (LOG_THREADS)
						{
							Log.i("GLThread", "waiting tid=" + getId());
						}
						_glThreadManager.wait();
					}
				} // end of synchronized(sGLThreadManager)

				/*
				 * Handle queued events
				 */
				if (eventsWaiting)
				{
					Runnable r;
					while ((r = getEvent()) != null)
					{
						r.run();
						if (isDone())
						{
							return;
						}
					}
					// Go back and see if we need to wait to render.
					continue;
				}

				if (needStart)
				{
					tellRendererSurfaceCreated = true;
					changed = true;
				}

				if (changed)
				{
					gl = (GL10) _eglHelper.createSurface(mHolder);
					tellRendererSurfaceChanged = true;
				}

				if (tellRendererSurfaceCreated)
				{
					_renderer.onSurfaceCreated(gl, _eglHelper.getEglConfig());
					tellRendererSurfaceCreated = false;
				}

				if (tellRendererSurfaceChanged)
				{
					_renderer.onSurfaceChanged(gl, w, h);
					tellRendererSurfaceChanged = false;
				}

				if ((w > 0) && (h > 0))
				{
					/* draw a frame here */
					_renderer.onDrawFrame(gl);

					/*
					 * Once we're done with GL, we need to call swapBuffers() to
					 * instruct the system to display the rendered frame
					 */
					_eglHelper.swap();
				}
			}
		} finally
		{
			/*
			 * clean-up everything...
			 */
			synchronized (_glThreadManager)
			{
				stopEglLocked();
				_eglHelper.finish();
			}
		}
	}

	private boolean isDone()
	{
		synchronized (_glThreadManager)
		{
			return _done;
		}
	}

	public void setRenderMode(int renderMode)
	{
		if (!((GLWallpaperService.GLEngine.RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY)))
		{
			throw new IllegalArgumentException("renderMode");
		}
		synchronized (_glThreadManager)
		{
			_renderMode = renderMode;
			if (renderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY)
			{
				_glThreadManager.notifyAll();
			}
		}
	}

	public int getRenderMode()
	{
		synchronized (_glThreadManager)
		{
			return _renderMode;
		}
	}

	public void requestRender()
	{
		synchronized (_glThreadManager)
		{
			_requestRender = true;
			_glThreadManager.notifyAll();
		}
	}

	public void surfaceCreated(SurfaceHolder holder)
	{
		mHolder = holder;
		synchronized (_glThreadManager)
		{
			if (LOG_THREADS)
			{
				Log.i("GLThread", "surfaceCreated tid=" + getId());
			}
			_hasSurface = true;
			_glThreadManager.notifyAll();
		}
	}

	public void surfaceDestroyed()
	{
		synchronized (_glThreadManager)
		{
			if (LOG_THREADS)
			{
				Log.i("GLThread", "surfaceDestroyed tid=" + getId());
			}
			_hasSurface = false;
			_glThreadManager.notifyAll();
			while (!_waitingForSurface && isAlive() && !_done)
			{
				try
				{
					_glThreadManager.wait();
				} catch (InterruptedException e)
				{
					Thread.currentThread().interrupt();
				}
			}
		}
	}

	public void onPause()
	{
		synchronized (_glThreadManager)
		{
			_paused = true;
			_glThreadManager.notifyAll();
		}
	}

	public void onResume()
	{
		synchronized (_glThreadManager)
		{
			_paused = false;
			_requestRender = true;
			_glThreadManager.notifyAll();
		}
	}

	public void onWindowResize(int w, int h)
	{
		synchronized (_glThreadManager)
		{
			_width = w;
			_height = h;
			_sizeChanged = true;
			_glThreadManager.notifyAll();
		}
	}

	public void requestExitAndWait()
	{
		// don't call this from GLThread thread or it is a guaranteed
		// deadlock!
		synchronized (_glThreadManager)
		{
			_done = true;
			_glThreadManager.notifyAll();
		}
		try
		{
			join();
		} catch (InterruptedException ex)
		{
			Thread.currentThread().interrupt();
		}
	}

	/**
	 * Queue an "event" to be run on the GL rendering thread.
	 * 
	 * @param r
	 *            the runnable to be run on the GL rendering thread.
	 */
	public void queueEvent(Runnable r)
	{
		synchronized (this)
		{
			_eventQueue.add(r);

			synchronized (_glThreadManager)
			{
				_eventsWaiting = true;
				_glThreadManager.notifyAll();
			}
		}
	}

	private Runnable getEvent()
	{
		synchronized (this)
		{
			if (_eventQueue.size() > 0)
			{
				return _eventQueue.remove(0);
			}

		}
		return null;
	}

	private class GLThreadManager
	{
		public synchronized void threadExiting(GLPaintingThread thread)
		{
			if (LOG_THREADS)
			{
				Log.i("GLThread", "exiting tid=" + thread.getId());
			}
			thread._done = true;
			if (_eglOwner == thread)
			{
				_eglOwner = null;
			}
			notifyAll();
		}

		/*
		 * Tries once to acquire the right to use an EGL surface. Does not
		 * block.
		 * 
		 * @return true if the right to use an EGL surface was acquired.
		 */
		public synchronized boolean tryAcquireEglSurface(GLPaintingThread thread)
		{
			// / ??? _eglOwner!= thread ??
			if (_eglOwner == thread || _eglOwner == null)
			{
				_eglOwner = thread;
				notifyAll();
				return true;
			}
			return false;
		}

		public synchronized void releaseEglSurface(GLPaintingThread thread)
		{
			if (_eglOwner == thread)
			{
				_eglOwner = null;
			}
			notifyAll();
		}
	}
}